import tkinter as tk
from tkinter import ttk, filedialog, scrolledtext, messagebox
import os
import threading
import json
import logging
from io import StringIO
import sys
import re
from typing import List, Dict, Optional, Any

# 导入标点修复器主类
from punctuation_fixer import PunctuationFixer, Change

class RedirectText:
    """将文本重定向到Tkinter文本控件"""
    def __init__(self, text_widget):
        self.text_widget = text_widget
        self.buffer = StringIO()

    def write(self, string):
        self.buffer.write(string)
        self.text_widget.configure(state="normal")
        self.text_widget.insert(tk.END, string)
        self.text_widget.see(tk.END)
        self.text_widget.configure(state="disabled")
    
    def flush(self):
        pass


class PunctuationFixerGUI:
    """标点符号修复工具的图形用户界面"""
    
    def __init__(self, root):
        """初始化GUI"""
        self.root = root
        self.root.title("标点符号修复工具")
        self.root.geometry("900x700")
        
        # 设置应用图标
        try:
            self.root.iconbitmap("icon.ico")  # 如果有图标文件，可以设置
        except:
            pass  # 没有图标文件则忽略
            
        # 创建主框架
        self.main_frame = ttk.Frame(root, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 设置网格权重
        self.main_frame.columnconfigure(0, weight=1)
        self.main_frame.rowconfigure(5, weight=1)  # 结果区域可以扩展
        
        # 添加控件
        self._create_file_selection()
        self._create_output_options()
        self._create_processing_options()
        self._create_buttons()
        self._create_result_area()
        self._create_status_bar()
        
        # 设置日志重定向
        self._setup_logging()
        
        # 标点修复器实例
        self.fixer = None
        
        # 正在处理的标志
        self.processing = False
        
        # 样式设置
        self._setup_styles()

    def _setup_styles(self):
        """设置界面样式"""
        style = ttk.Style()
        
        # 主题设置
        try:
            style.theme_use("clam")  # 尝试使用clam主题
        except:
            pass  # 如果不可用则使用默认主题
        
        # 按钮样式
        style.configure("Primary.TButton", 
                        font=("微软雅黑", 10, "bold"),
                        background="#4CAF50")
        
        style.configure("Secondary.TButton", 
                        font=("微软雅黑", 10))
        
        # 标签样式
        style.configure("TLabel", 
                        font=("微软雅黑", 10))
        
        style.configure("Header.TLabel", 
                        font=("微软雅黑", 11, "bold"))
        
        # 选项框样式
        style.configure("TCheckbutton", 
                        font=("微软雅黑", 10))
        
        # 组框样式
        style.configure("TLabelframe", 
                        font=("微软雅黑", 10, "bold"))
        style.configure("TLabelframe.Label", 
                        font=("微软雅黑", 10, "bold"))
        
    def _create_file_selection(self):
        """创建文件选择部分"""
        file_frame = ttk.LabelFrame(self.main_frame, text="文件选择", padding="5")
        file_frame.grid(row=0, column=0, sticky="we", padx=5, pady=5)
        
        # 输入文件选择
        ttk.Label(file_frame, text="输入文件:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        
        self.files_entry = ttk.Entry(file_frame)
        self.files_entry.grid(row=0, column=1, sticky="we", padx=5, pady=5)
        
        files_button = ttk.Button(file_frame, text="选择文件", command=self._select_files)
        files_button.grid(row=0, column=2, padx=5, pady=5)
        
        # 配置文件选择
        ttk.Label(file_frame, text="配置文件:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        
        self.config_entry = ttk.Entry(file_frame)
        self.config_entry.grid(row=1, column=1, sticky="we", padx=5, pady=5)
        
        config_button = ttk.Button(file_frame, text="选择配置", command=self._select_config)
        config_button.grid(row=1, column=2, padx=5, pady=5)
        
        # 设置文件框架的列权重
        file_frame.columnconfigure(1, weight=1)

    def _create_output_options(self):
        """创建输出选项部分"""
        output_frame = ttk.LabelFrame(self.main_frame, text="输出选项", padding="5")
        output_frame.grid(row=1, column=0, sticky="we", padx=5, pady=5)
        
        # 输出目录
        ttk.Label(output_frame, text="输出目录:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        
        self.output_entry = ttk.Entry(output_frame)
        self.output_entry.grid(row=0, column=1, sticky="we", padx=5, pady=5)
        
        output_button = ttk.Button(output_frame, text="选择目录", command=self._select_output_dir)
        output_button.grid(row=0, column=2, padx=5, pady=5)
        
        # 报告文件
        ttk.Label(output_frame, text="报告文件:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
        
        self.report_entry = ttk.Entry(output_frame)
        self.report_entry.grid(row=1, column=1, sticky="we", padx=5, pady=5)
        
        report_button = ttk.Button(output_frame, text="选择位置", command=self._select_report_file)
        report_button.grid(row=1, column=2, padx=5, pady=5)
        
        # 报告格式
        ttk.Label(output_frame, text="报告格式:").grid(row=2, column=0, sticky="w", padx=5, pady=5)
        
        self.report_format = tk.StringVar(value="text")
        report_format_frame = ttk.Frame(output_frame)
        report_format_frame.grid(row=2, column=1, sticky="w", padx=5, pady=5)
        
        ttk.Radiobutton(report_format_frame, text="文本", variable=self.report_format, value="text").pack(side=tk.LEFT, padx=5)
        ttk.Radiobutton(report_format_frame, text="Markdown", variable=self.report_format, value="markdown").pack(side=tk.LEFT, padx=5)
        ttk.Radiobutton(report_format_frame, text="JSON", variable=self.report_format, value="json").pack(side=tk.LEFT, padx=5)
        
        # 设置输出框架的列权重
        output_frame.columnconfigure(1, weight=1)

    def _create_processing_options(self):
        """创建处理选项部分"""
        options_frame = ttk.LabelFrame(self.main_frame, text="处理选项", padding="5")
        options_frame.grid(row=2, column=0, sticky="we", padx=5, pady=5)
        
        # 预览模式
        self.preview_var = tk.BooleanVar(value=False)
        preview_check = ttk.Checkbutton(options_frame, text="仅预览更改，不修改文件", variable=self.preview_var)
        preview_check.grid(row=0, column=0, sticky="w", padx=5, pady=5)
        
        # 备份选项
        self.backup_var = tk.BooleanVar(value=True)
        backup_check = ttk.Checkbutton(options_frame, text="创建备份文件(.bak)", variable=self.backup_var)
        backup_check.grid(row=0, column=1, sticky="w", padx=5, pady=5)
        
        # 详细日志
        self.verbose_var = tk.BooleanVar(value=False)
        verbose_check = ttk.Checkbutton(options_frame, text="显示详细日志", variable=self.verbose_var)
        verbose_check.grid(row=1, column=0, sticky="w", padx=5, pady=5)

    def _create_buttons(self):
        """创建操作按钮"""
        buttons_frame = ttk.Frame(self.main_frame)
        buttons_frame.grid(row=3, column=0, sticky="we", padx=5, pady=10)
        
        # 执行按钮
        self.run_button = ttk.Button(
            buttons_frame, 
            text="执行标点修复", 
            command=self._run_processing,
            style="Primary.TButton"
        )
        self.run_button.pack(side=tk.LEFT, padx=5)
        
        # 清除按钮
        clear_button = ttk.Button(
            buttons_frame, 
            text="清除结果", 
            command=self._clear_results,
            style="Secondary.TButton"
        )
        clear_button.pack(side=tk.LEFT, padx=5)
        
        # 帮助按钮
        help_button = ttk.Button(
            buttons_frame, 
            text="帮助", 
            command=self._show_help,
            style="Secondary.TButton"
        )
        help_button.pack(side=tk.RIGHT, padx=5)

    def _create_result_area(self):
        """创建结果显示区域"""
        result_frame = ttk.LabelFrame(self.main_frame, text="处理结果和日志", padding="5")
        result_frame.grid(row=5, column=0, sticky="nswe", padx=5, pady=5)
        
        # 设置结果框架的行和列权重
        result_frame.columnconfigure(0, weight=1)
        result_frame.rowconfigure(0, weight=1)
        
        # 使用Notebook创建选项卡
        self.result_notebook = ttk.Notebook(result_frame)
        self.result_notebook.grid(row=0, column=0, sticky="nswe")
        
        # 日志选项卡
        self.log_frame = ttk.Frame(self.result_notebook)
        self.result_notebook.add(self.log_frame, text="日志")
        
        # 设置日志选项卡的行和列权重
        self.log_frame.columnconfigure(0, weight=1)
        self.log_frame.rowconfigure(0, weight=1)
        
        # 日志文本区域
        self.log_text = scrolledtext.ScrolledText(self.log_frame, wrap=tk.WORD, height=15)
        self.log_text.grid(row=0, column=0, sticky="nswe", padx=5, pady=5)
        self.log_text.configure(state="disabled")
        
        # 预览选项卡
        self.preview_frame = ttk.Frame(self.result_notebook)
        self.result_notebook.add(self.preview_frame, text="预览/结果")
        
        # 设置预览选项卡的行和列权重
        self.preview_frame.columnconfigure(0, weight=1)
        self.preview_frame.rowconfigure(0, weight=1)
        
        # 预览文本区域
        self.preview_text = scrolledtext.ScrolledText(self.preview_frame, wrap=tk.WORD, height=15)
        self.preview_text.grid(row=0, column=0, sticky="nswe", padx=5, pady=5)
        self.preview_text.configure(state="disabled")

    def _create_status_bar(self):
        """创建状态栏"""
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self.main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.grid(row=6, column=0, sticky="we", padx=5, pady=2)

    def _setup_logging(self):
        """设置日志重定向到GUI文本框"""
        # 创建自定义处理程序，将日志输出到文本控件
        log_handler = logging.StreamHandler(RedirectText(self.log_text))
        log_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
        
        # 获取根日志记录器并添加处理程序
        root_logger = logging.getLogger()
        root_logger.addHandler(log_handler)
        root_logger.setLevel(logging.INFO)
        
        # 获取标点修复器日志记录器并添加处理程序
        fixer_logger = logging.getLogger('PunctuationFixer')
        fixer_logger.addHandler(log_handler)
        fixer_logger.setLevel(logging.INFO)

    def _select_files(self):
        """选择输入文件"""
        files = filedialog.askopenfilenames(
            title="选择要处理的文件",
            filetypes=[("TeX文件", "*.tex"), ("所有文件", "*.*")]
        )
        if files:
            self.files_entry.delete(0, tk.END)
            self.files_entry.insert(0, ";".join(files))

    def _select_config(self):
        """选择配置文件"""
        config_file = filedialog.askopenfilename(
            title="选择配置文件",
            filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")]
        )
        if config_file:
            self.config_entry.delete(0, tk.END)
            self.config_entry.insert(0, config_file)

    def _select_output_dir(self):
        """选择输出目录"""
        output_dir = filedialog.askdirectory(title="选择输出目录")
        if output_dir:
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, output_dir)

    def _select_report_file(self):
        """选择报告文件位置"""
        report_file = filedialog.asksaveasfilename(
            title="选择报告文件位置",
            filetypes=[
                ("文本文件", "*.txt"), 
                ("Markdown文件", "*.md"), 
                ("JSON文件", "*.json"), 
                ("所有文件", "*.*")
            ]
        )
        if report_file:
            self.report_entry.delete(0, tk.END)
            self.report_entry.insert(0, report_file)

    def _clear_results(self):
        """清除结果区域内容"""
        self.preview_text.configure(state="normal")
        self.preview_text.delete(1.0, tk.END)
        self.preview_text.configure(state="disabled")
        
        self.log_text.configure(state="normal")
        self.log_text.delete(1.0, tk.END)
        self.log_text.configure(state="disabled")
        
        self.status_var.set("就绪")

    def _show_help(self):
        """显示帮助信息"""
        help_text = """标点符号修复工具 - 帮助

功能说明：
此工具用于自动识别并修复中英文混合文本中的标点符号错误，特别适用于LaTeX源码文档。
工具会智能判断文本的语言环境，根据上下文将标点符号修正为合适的形式。

使用方法：
1. 在"文件选择"部分选择要处理的文件，可以选择多个文件
2. 可选择配置文件来自定义标点映射和忽略模式
3. 在"输出选项"部分可设置输出目录、报告文件和报告格式
4. 在"处理选项"部分可以设置是否仅预览、是否创建备份等
5. 点击"执行标点修复"按钮开始处理
6. 在"处理结果和日志"选项卡中查看处理结果

注意事项：
- 预览模式下不会实际修改文件，只会生成修改预览
- 建议首次使用时启用预览模式，确认修改无误后再实际执行
- 处理大文件可能需要一些时间，请耐心等待
- 默认会创建备份文件(.bak后缀)，以防需要恢复原文件

更多详细信息，请参考项目文档。
"""
        messagebox.showinfo("帮助", help_text)

    def _validate_inputs(self):
        """验证输入是否有效"""
        # 检查是否选择了文件
        if not self.files_entry.get():
            messagebox.showerror("错误", "请选择要处理的文件")
            return False
        
        # 检查所有选择的文件是否存在
        files = self.files_entry.get().split(";")
        for file in files:
            if not os.path.exists(file):
                messagebox.showerror("错误", f"文件不存在: {file}")
                return False
        
        # 检查配置文件（如果指定）
        config_file = self.config_entry.get()
        if config_file and not os.path.exists(config_file):
            messagebox.showerror("错误", f"配置文件不存在: {config_file}")
            return False
        
        # 检查输出目录（如果指定）
        output_dir = self.output_entry.get()
        if output_dir and not os.path.exists(output_dir):
            try:
                os.makedirs(output_dir)
            except Exception as e:
                messagebox.showerror("错误", f"无法创建输出目录: {str(e)}")
                return False
        
        return True

    def _update_status(self, status):
        """更新状态栏"""
        self.status_var.set(status)
        self.root.update_idletasks()

    def _append_to_preview(self, text):
        """向预览文本区域添加文本"""
        self.preview_text.configure(state="normal")
        self.preview_text.insert(tk.END, text + "\n")
        self.preview_text.see(tk.END)
        self.preview_text.configure(state="disabled")
        self.root.update_idletasks()

    def _run_processing(self):
        """执行标点修复处理"""
        # 检查输入
        if not self._validate_inputs():
            return
            
        # 防止重复执行
        if self.processing:
            messagebox.showinfo("提示", "已有处理任务正在执行")
            return
            
        # 设置日志级别
        log_level = logging.DEBUG if self.verbose_var.get() else logging.INFO
        logging.getLogger().setLevel(log_level)
        logging.getLogger('PunctuationFixer').setLevel(log_level)
        
        # 清空结果区域
        self._clear_results()
        
        # 获取选项
        files = self.files_entry.get().split(";")
        config_file = self.config_entry.get() or None
        output_dir = self.output_entry.get() or None
        report_file = self.report_entry.get() or None
        report_format = self.report_format.get()
        preview_only = self.preview_var.get()
        create_backup = self.backup_var.get()
        
        # 更新状态
        self._update_status("正在处理...")
        self.run_button.configure(state="disabled")
        self.processing = True
        
        # 切换到日志选项卡
        self.result_notebook.select(0)
        
        # 在单独的线程中执行处理
        threading.Thread(target=self._process_files, args=(
            files, config_file, output_dir, report_file,
            report_format, preview_only, create_backup
        )).start()

    def _process_files(self, files, config_file, output_dir, report_file,
                      report_format, preview_only, create_backup):
        """在单独的线程中处理文件"""
        try:
            # 创建修复器实例
            self.fixer = PunctuationFixer(config_file=config_file)
            
            # 处理文件
            results = self.fixer.process_files(
                files,
                output_dir=output_dir,
                backup=create_backup,
                preview_only=preview_only
            )
            
            # 生成报告
            report = self.fixer.generate_report(results, report_file, report_format)
            
            # 显示报告到预览区域
            self.root.after(0, lambda: self._show_report(report, results))
            
            # 更新状态
            total_changes = sum(result.get('total_changes', 0) for result in results)
            status_msg = f"处理完成。处理了{len(files)}个文件，共{total_changes}处更改"
            self.root.after(0, lambda: self._update_status(status_msg))
            
            # 如果是预览模式，切换到预览选项卡
            if preview_only:
                self.root.after(0, lambda: self.result_notebook.select(1))
                
        except Exception as e:
            # 记录错误
            logging.error(f"处理过程中出错: {str(e)}")
            self.root.after(0, lambda: messagebox.showerror("错误", f"处理过程中出错: {str(e)}"))
            self.root.after(0, lambda: self._update_status("处理出错"))
        finally:
            # 恢复UI状态
            self.root.after(0, lambda: self.run_button.configure(state="normal"))
            self.processing = False

    def _show_report(self, report, results):
        """显示处理报告"""
        # 清空预览文本区域
        self.preview_text.configure(state="normal")
        self.preview_text.delete(1.0, tk.END)
        
        # 添加报告内容
        if isinstance(report, str):
            self.preview_text.insert(tk.END, report)
        else:
            self.preview_text.insert(tk.END, str(report))
        
        self.preview_text.configure(state="disabled")
        
        # 如果报告文件已生成，显示消息
        if any('report' in result and result['report'] for result in results if isinstance(result, dict)):
            messagebox.showinfo("完成", "报告已生成并保存到指定位置")


def main():
    """应用主函数"""
    root = tk.Tk()
    app = PunctuationFixerGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()
